6604 2020-01-15 2024-10-14

本篇是Spring MVC基础篇,Spring MVC系列共4篇,四篇都是相辅相成的。当读者纵观Spring系列所有文章/来来回回/拼拼凑凑看完Spring MVC系列后,相信对Spring的理解肯定会上升n个档次(n与你现在的水平负相关且n > 1)。

注:本系列所有的结论都是从源码出发,都是经历过无数次debug得出的(由于历史原因,有部分代码是4.x的,后面看的是5.x,可能些许出入,请读者知悉)。限于本人水平有限,如发现不对的地方,请联系我!谢谢!

一、概述

上篇说道ApplicationContext的功能比BeanFactory多,但具体多出那些功能还需要我们进一步探索。先从一个简单的xml文件开始。

  • 使用BeanFactory方式加载xml,那么代码大致是这样的
Resource resource = new ClassPathResource("xiaokui/xiaokui.xml");
BeanFactory xmlBeanFactory = new XmlBeanFactory(resource);
TestBean testBean = (TestBean) xmlBeanFactory.getBean("testBean");
  • 而使用ApplicationContext方式加载xml,代码大致是这样的
// 代码精致了一丢丢
ApplicationContext application = new ClassPathXmlApplicationContext("xiaokui/xiaokui.xml");
TestBean testBean = (TestBean) application.getBean("testBean");

ClassPathXMLApplicationContext 作为切入点(AnnotationConfigApplicationContext同,核心都是refresh方法),开始对整体功能进行分析,关键代码如下

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
	private Resource[] configResources;
	
    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }

    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
        // 往上嵌套了好几个super
        super(parent);
        // 解析路径
        setConfigLocations(configLocations);
        // 刷新上下文
        if (refresh) {
            // 重点看下这个
            refresh();
        }
    }
    
    /**
     * Set the config locations for this application context.
     * <p>If not set, the implementation may use a default as appropriate.
     */
    public void setConfigLocations(String[] locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                // 调用父类方法解析路径,如果字符串种包含特殊符号,如${var},那么在此方法种会搜寻匹配的系统变量并替换
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
    }    
}

二、refresh方法**

确定好配置文件路径后,便可以根据路径做配置文件的解析以及各种功能的实现了。可以说,refresh方法几乎包含ApplicationContext种提供的全部功能,而且此方法种逻辑非常清晰明了,代码如下

// 来自父类 AbstractApplicationContext,多个实现类共用逻辑
// 这里为后文做一个铺垫,读者后面需要回到这里的
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        // 准备刷新的上下文环境
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        // 告诉子类刷新内部bean工厂
        // 初始化DefaultListableBeanFactory,并进行xml文件读取,
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        // 对BeanFactory进行各种功能填充
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            // 子类覆盖方法做前置处理
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            // 激活各种BeanFactory处理器
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            // 注册拦截bean创建的bean处理器,这里只是注册,真正的调用是在getBean的时候
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            // 初始化应用消息广播器,并放入applicationEventMulticaster bean中
            initMessageSource();

            // Initialize event multicaster for this context.
            // 准备事件广播
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            // 留个子类来初始化其他的bean
            onRefresh();

            // Check for listener beans and register them.
            // 在所有注册的bean种查找listener bean,注册到消息广播中
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            // 初始化剩下的单实例(非惰性)
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            // 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人
            finishRefresh();
        }
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }
        finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
		}
    }
}

下面概括一下 AbstractApplicationContext 初始化的步骤,并从中解释一下它为我们提供的功能。

  1. 初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证。

在某些情况下项目的使用需要读取某些系统变量,而这个变量的设置很可能会影响着系统的正确性,那么这个准备方法就显得非常必要了,它可以在Spring启动的时候提前对必须的变量进行存在性验证。

  1. 初始化BeanFactory,并进行xml文件读取/bean扫描读取

之前有提到ClassPathXMLApplicationContext包含着BeanFactory所提供的一切特征,那么在这一步骤种将会复用BeanFactory中的配置文件读取解析及其他功能,这一步之后,ClassPathXMLApplicationContext实际上已经包含了BeanFactory所提供的功能,也就是可以进行bean的提取等基础操作了。

  1. 对BeanFactory进行各种功能填充

@Qualifier与@Autowired应该是大家非常熟悉的注解,那么这两个注解正式在这一步骤种增加支持的。

  1. 子类覆盖方法做额外的处理

Spring之所以强大,为世人所推崇,除了它功能上为大家提供了便利外,还有一方面是它的完美架构,开放式的架构让使用它的程序员很容易根据业务需要扩展已经存在的功能。这种开放式的设计在Spring中随处可见。

  1. 激活各种BeanFactory处理器。
  2. 注册拦截bean所创建的bean处理器,这里只是注册,真正的调用是在getBean的时候。
  3. 为上下文初始化Message源,即对不同的语言的消息体进行国际化处理。
  4. 初始化应用消息广播器,并放入applicationEventMulticaster bean中。
  5. 留个子类来初始化其他的bean。
  6. 在所有注册的bean中查找listener bean,注册到消息广播器中。
  7. 初始化剩下的单实例(非惰性的)。
  8. 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同事发出ContextRefreshEvent通知别人。

接下来我们一步一步看,观察Spring在每一个环节都做了哪些事情。

三、prepareRefresh

prepareRefresh方法主要是做些准备工作,如对系统属性及环境变量的初始化及验证

protected void prepareRefresh() {
    this.startupDate = System.currentTimeMillis();

    synchronized (this.activeMonitor) {
        this.active = true;
    }

    if (logger.isInfoEnabled()) {
        logger.info("Refreshing " + this);
    }

    // Initialize any placeholder property sources in the context environment
    // 初始化环境属性,抽象方法,留给子类实现,子类实现均是下面代码
    // 来源于类 AbstractRefreshableWebApplicationContext

    // 注意这里的 Environment之前已被初始化,下篇中将会有详细说明
	//	ConfigurableEnvironment env = getEnvironment();
	//	if (env instanceof ConfigurableWebEnvironment) {
	//		((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, this.servletConfig);
	//	}

    initPropertySources();

    // Validate that all properties marked as required are resolvable
    // see ConfigurablePropertyResolver#setRequiredProperties
    // 验证需要的属性是否都已放入环境中
    getEnvironment().validateRequiredProperties();
}

@Override
protected void initPropertySources() {
    ConfigurableEnvironment env = getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(this.servletContext, this.servletConfig);
    }
}

四、obtainFreshBeanFactory

obtainFreshBeanFactory方法从字面理解是获取新鲜的BeanFactory。经过这个方法后,ApplicationContext拥有了BeanFactory的全部功能。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    // 初始化BeanFactory,并进行xml文件读取,并将得到的BeanFactory记录在当前实体的属性中
    refreshBeanFactory();
    // 返回当前实体的beanFactory属性
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (logger.isDebugEnabled()) {
        logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}

// 核心实现委托给了refreshBeanFactory,在本类中是个抽象方法
// 来自类 AbstractRefreshableApplicationContext,继承自本类AbstractApplicationContext
@Override
protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        // 创建DefaultListableBeanFactory
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        // 为了序列化指定id,如果需要的话,让这个BeanFactory从id反序列化到BeanFactory对象
        beanFactory.setSerializationId(getId());
        
        // 定制beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象以及循环依赖
        customizeBeanFactory(beanFactory);
        
        // 初始化DodumentReader,并进行xml文件读取及解析
        // 重点在这一步,之前是从xml读取,现在也支持从注解读取了
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

这一步很关键,我们详细分析上面的每个步骤。

  1. 创建DefaultListableBeanFactory。DefaultListableBeanFactory是Spring中的发动机,这个在开局第二篇就重点提过,有了这个,那么BeanFactory就是ApplicationContext的子集了

  2. 指定序列化ID

  3. 定制BeanFactory

  4. 加载BeanDefinition

  5. 使用全局变量记录BeanFactory类实例。

1、customizeBeanFactory

这里已经开始了对BeanFactory的扩展,在基本容器的基础上,增加了是否允许覆盖、是否允许扩展的设置。

// 来自类 AbstractRefreshableApplicationContext
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
    // 此属性的含义:是否允许覆盖相同名称的不同定义的对象,默认为null
    if (this.allowBeanDefinitionOverriding != null) {
        beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // 此属性的含义:是否允许bean之间存在循环依赖,默认为null
    if (this.allowCircularReferences != null) {
        beanFactory.setAllowCircularReferences(this.allowCircularReferences);
    }
}

对于允许覆盖和允许依赖的设置这里只是判断了是否为空,如果不为空要进行设置,但是这里并没有看到在哪里进行设置,究竟这个设置是在哪里进行设置的呢?答案是由子类重写覆盖。

2、loadBeanDefinitions*

这个方法决定了从哪加载BeanDefinitions,但在本类中是个抽象方法,主要由以下三个子类实现

  • XmlWebApplicationContext:Spring提供已有的xml加载bean的WebApplicationContext。
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    // Configure the bean definition reader with this context's
    // resource loading environment.
    beanDefinitionReader.setEnvironment(getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    // Allow a subclass to provide custom initialization of the reader,
    // then proceed with actually loading the bean definitions.
    // 这一步是空实现
    initBeanDefinitionReader(beanDefinitionReader);
    // 关键在于这一步
    loadBeanDefinitions(beanDefinitionReader);
}

// 来源于类 XmlWebApplicationContext
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    // 注意这里的 getConfigLocations,跟一下
    // 在这里值为 /WEB-INF/applicationContext.xml
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            // 开始下狠手了
            reader.loadBeanDefinitions(configLocation);
        }
    }
}


  • AbstractXmlApplicationContext:Spring提供已有的xml加载bean的ApplicationContext,不限于Web场景,虽然是抽象类但这个类其实没有抽象方法,子类可以重写加载路径已实现不同的功能(如本地、远程),XmlWebApplicationContext就是使用它原有的方法。

  • AnnotationConfigWebApplicationContext:根据注解加载bean的WebApplicationContext,后面可能会用到,这里简单提一下。

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
    AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory);
    ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory);

    BeanNameGenerator beanNameGenerator = getBeanNameGenerator();
    if (beanNameGenerator != null) {
        reader.setBeanNameGenerator(beanNameGenerator);
        scanner.setBeanNameGenerator(beanNameGenerator);
        beanFactory.registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, beanNameGenerator);
    }

    ScopeMetadataResolver scopeMetadataResolver = getScopeMetadataResolver();
    if (scopeMetadataResolver != null) {
        reader.setScopeMetadataResolver(scopeMetadataResolver);
        scanner.setScopeMetadataResolver(scopeMetadataResolver);
    }

    if (!this.annotatedClasses.isEmpty()) {
        if (logger.isInfoEnabled()) {
            logger.info("Registering annotated classes: [" +
                    StringUtils.collectionToCommaDelimitedString(this.annotatedClasses) + "]");
        }
        reader.register(ClassUtils.toClassArray(this.annotatedClasses));
    }

    if (!this.basePackages.isEmpty()) {
        if (logger.isInfoEnabled()) {
            logger.info("Scanning base packages: [" +
                    StringUtils.collectionToCommaDelimitedString(this.basePackages) + "]");
        }
        scanner.scan(StringUtils.toStringArray(this.basePackages));
    }

    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            try {
                Class<?> clazz = ClassUtils.forName(configLocation, getClassLoader());
                if (logger.isInfoEnabled()) {
                    logger.info("Successfully resolved class for [" + configLocation + "]");
                }
                reader.register(clazz);
            }
            catch (ClassNotFoundException ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not load class for config location [" + configLocation +
                            "] - trying package scan. " + ex);
                }
                int count = scanner.scan(configLocation);
                if (logger.isInfoEnabled()) {
                    if (count == 0) {
                        logger.info("No annotated classes found for specified class/package [" + configLocation + "]");
                    }
                    else {
                        logger.info("Found " + count + " annotated classes in package [" + configLocation + "]");
                    }
                }
            }
        }
    }
}

3、getConfigLocations

protected String[] getConfigLocations() {
    // 对于Root WebApplicationContext的configLocations为applicationContext.xml
    // 而对于Servlet WebApplicationContext的configLocations为null,所以逻辑走下一步
    return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
}

@Override
protected String[] getDefaultConfigLocations() {
    if (getNamespace() != null) {
        // 默认为 /WEB-INF/dispatcher-servlet.xml
        return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
    }
    else {
        return new String[] {DEFAULT_CONFIG_LOCATION};
    }
}

五、prepareBeanFactory*

进入方法prepareBeanFactory前,Spring已经完成了对配置的解析,获得一个完整的DefaultListableBeanFactory对象且获取到了框架内完整的bean列表(包括用户定义和非用户定义),而ApplicationContext在功能上的扩展也由这一步展开。

/**
 * Configure the factory's standard context characteristics,
 * such as the context's ClassLoader and post-processors.
 * @param beanFactory the BeanFactory to configure
 */
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // Tell the internal bean factory to use the context's class loader etc.
    // 设置BeanFactory的classLoader为当前Context的classLoader
    beanFactory.setBeanClassLoader(getClassLoader());
    // 设置BeanFactory的表达式语言处理器,Spring3增加了表达式语言的支持
    // 默认可以使用#{bean.xxx}的形式来调用相关属性值
    beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver());
    // 为BeanFactory增加一个默认的propertyEditor,这个主要是对bean的属性等设置管理的一个工具
    beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

    // Configure the bean factory with context callbacks.
    // 添加beanPostProcessoer
    beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
    
    // 设置了几个忽略自动装配的接口
    beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
    beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
    beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
    beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
    beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

    // BeanFactory interface not registered as resolvable type in a plain factory.
    // MessageSource registered (and found for autowiring) as a bean.
    // 设置了几个自动装配的特殊规则
    beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
    beanFactory.registerResolvableDependency(ResourceLoader.class, this);
    beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
    beanFactory.registerResolvableDependency(ApplicationContext.class, this);

    // Detect a LoadTimeWeaver and prepare for weaving, if found.
    // 增加对AspectJ的支持
    if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
        beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
        // Set a temporary ClassLoader for type matching.
        beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
    }

    // Register default environment beans.
    // 为特定beanName添加默认的系统环境bean
    if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
        beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
    }
    if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
        beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
    }
    if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
        beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
    }
}

上面方法中主要进行了几个方面的扩展。

  1. 增加对SPEL语言的支持。
  2. 增加对属性编辑器的支持。
  3. 增加对一些内置类,比如EnvironmentAware、MessageSourceAware的注入。
  4. 设置了依赖功能可忽略的接口。
  5. 注册一些固定依赖的属性。
  6. 增加AspectJ的支持。
  7. 将相关环境变量及属性注册以单例模式注册。

下面对每一步进行详细分析。

1、增加对SPEL语言的支持

Spring表达式语言全称为Spring Expression Language,缩写为SPEL,能在运行时构建复杂表达式、存储对象图属性、对象方法调用等,并且能与Spring功能完美整合,比如能用来配置bean定义。SPEL是单独模块,只依赖于core模块,可以单独使用。

SPEL使用#{...}作为定界符,所有在大括号中的字符都将被认为是SPEL,使用格式如下:

<property name="another" value="#{another}"/>
等同于
<property name="another" ref="another"/>

在源码中通过代码beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver());注册语言解析器,就可以对SPEL进行解析了,那么在注册解析器后Spring又是在什么时候调用这个解析器进行解析呢?

之前我们将结果Spring在bean进行初始化的时候会有属性填充这一步,而在这一步中Spring会调用 AbstractAutowireCapableBeanFactory 类的 applyPropertyValues 方法来完成功能。

就在这个方法中,会通过构造BeanDefinitionValueResolver类型实例valueResolver来进行属性值的解析。同时,也是在这个步骤中一般通过AbstractBeanFactory中的evaluateBeanDefinitionString方法去完成SPEL的解析。

protected Object evaluateBeanDefinitionString(String value, BeanDefinition beanDefinition) {
    if (this.beanExpressionResolver == null) {
        return value;
    }
    Scope scope = (beanDefinition != null ? getRegisteredScope(beanDefinition.getScope()) : null);
    return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope));
}

当调用这个方法时会判断是否存在语言解析器,如果存在则调用语言解析器的方法进行解析,解析的过程是在Spring的expression的包内,这里不深究。我们通过查看对evaluateBeanDefinitionString方法的调用层次可以看出,应用语言解析器的调用主要是在解析依赖注入bean的时候,以及在完成bean的初始化和属性获取后进行的属性填充的时候。

2、增加属性注册编辑器

在Spring DI注入的时候可以把普通属性注入进来,但是像Date类型就无法被识别。例如

<property name="date" value="2018-07-09"/>

如果直接获取该Date,程序会报异常,类型转换不成功。因为String无法转换为Date类型。Spring针对此问题提供了两种解决方法。

一、使用自定义属性编辑器

使用自定义属性编辑器,通过继承PropertyEditorSupport,重写setAsText方法,具体步骤如下。

  1. 编写自定义的属性编辑器
public class DatePropertyEditor extends PropertyEditorSupport {
    private String format = "yyyy-MM-dd";
    public void setFormat(String format) {
        this.format = format;
    }
    @Override
    public void setAsText(String str) {
        System.out.println(str);
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        try {
            Date d = sdf.parse(str);
            this.setValue(d);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }    
}
  1. 将自定义的属性编辑器注册到Spring中。
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="java.util.Date">
                <bean class="xiaokui.DatePropertyEditor">
                    <property name="format" value="yyyy-MM-dd"/>
                </bean>
            </entry>
        </map>
    </property>
</bean>

在配置文件中引入类型为CustomEditorConfigurer的bean,并在属性customEditors中加入自定义的属性编辑器,其中key为属性编辑器所对应的类型。通过这样的配置,当Spring在注入bean的属性时一旦遇到了java.util.Date类型的属性会自动调用自定义的DatePropertyEditor解析器进行解析,并且解析结果代替配置属性进行注入。

二、注册Spring自带的属性编辑器CustomDateEditor

通过注册Spring自带的属性编辑器CustomDateEditor,具体步骤如下。

  1. 定义属性编辑器。
public class DatePropertyEditorRegistrar implements PropertyEditorRegistrar {
    @Override
    public void registerCustomEditors(PropertyEditorRegistry registry) {
        registry.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
    }
}
  1. 注册到Spring中。
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="propertyEditorRegistrars">
        <list>
            <bean class="xiaokui.DatePropertyEditorRegistrar"/>
        </list>
    </property>
</bean>

通过在配置文件中将自定义的DatePropertyEditorRegistrar注册进入CustomEditorConfigurer的propertyEditorRegistrars属性中,可以起到同样的效果。

我们不妨深入探索一下ResourceEditorRegistrar的内部实现,在ResourceEditorRegistrar中,我们最关心的方法是registerCustomEditors。

public void registerCustomEditors(PropertyEditorRegistry registry) {
    ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver);
    doRegisterEditor(registry, Resource.class, baseEditor);
    doRegisterEditor(registry, ContextResource.class, baseEditor);
    doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor));
    doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor));
    doRegisterEditor(registry, File.class, new FileEditor(baseEditor));
    doRegisterEditor(registry, URL.class, new URLEditor(baseEditor));

    ClassLoader classLoader = this.resourceLoader.getClassLoader();
    doRegisterEditor(registry, URI.class, new URIEditor(classLoader));
    doRegisterEditor(registry, Class.class, new ClassEditor(classLoader));
    doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader));

    if (this.resourceLoader instanceof ResourcePatternResolver) {
        doRegisterEditor(registry, Resource[].class,
                new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver));
    }
}
// 来自于AbstractBeanFactory,两个方法同属一类
protected void initBeanWrapper(BeanWrapper bw) {
    // 一般使用 ConversionService 来代替 PropertyEditor
    // PropertyEditor有很大的局限性,只能进行String->Object的转换
    bw.setConversionService(getConversionService());
    registerCustomEditors(bw);
}

其中我们看到一个方法是我们熟悉的,就是AbstractBeanFactory类的initBeanWrapper方法,这是在bean初始化时使用的一个方法,之前已经使用过大量篇幅进行讲解了,主要是在将BeanDefinition转换为BeanWrapper后用于对属性的填充。到此,逻辑已经明了,在bean的初始化后会调用ResourceEditorRegistrar的registerCustomEditors方法进行批量的通用属性编辑器注册。注册后,在属性填充的环节便可以直接让Spring使用这些编辑器进行属性的解析了。

既然提到了BeanWrapper,这里也有必要强调下,Spring中用于封装bean的是BeanWrapper,而它又间接继承了PropertyEditorRegistrar类型,也就是我们之前反复看到的方法参数PropertyEditorRegistry registry,其实大部分情况下都是BeanWrapper,对于BeanWrapper在Spring中的默认实现是 BeanWrapperImp,而BeanWrapperImp除了实现BeanWrapper接口外,还继承了 PropertyEditorRegistrySupport,在 PropertyEditorRegistrySupport 中有这样一个方法,看完之后可能会有点不一样的感受。

private void createDefaultEditors() {
    this.defaultEditors = new HashMap<Class<?>, PropertyEditor>(64);

    // Simple editors, without parameterization capabilities.
    // The JDK does not contain a default editor for any of these target types.
    this.defaultEditors.put(Charset.class, new CharsetEditor());
    this.defaultEditors.put(Class.class, new ClassEditor());
    this.defaultEditors.put(Class[].class, new ClassArrayEditor());
    this.defaultEditors.put(Currency.class, new CurrencyEditor());
    this.defaultEditors.put(File.class, new FileEditor());
    this.defaultEditors.put(InputStream.class, new InputStreamEditor());
    this.defaultEditors.put(InputSource.class, new InputSourceEditor());
    this.defaultEditors.put(Locale.class, new LocaleEditor());
    this.defaultEditors.put(Pattern.class, new PatternEditor());
    this.defaultEditors.put(Properties.class, new PropertiesEditor());
    this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
    this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
    this.defaultEditors.put(URI.class, new URIEditor());
    this.defaultEditors.put(URL.class, new URLEditor());
    this.defaultEditors.put(UUID.class, new UUIDEditor());

    // Default instances of collection editors.
    // Can be overridden by registering custom instances of those as custom editors.
    this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
    this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
    this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
    this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
    this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));

    // Default editors for primitive arrays.
    this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
    this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());

    // The JDK does not contain a default editor for char!
    this.defaultEditors.put(char.class, new CharacterEditor(false));
    this.defaultEditors.put(Character.class, new CharacterEditor(true));

    // Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
    this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
    this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));

    // The JDK does not contain default editors for number wrapper types!
    // Override JDK primitive number editors with our own CustomNumberEditor.
    this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
    this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
    this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
    this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
    this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
    this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
    this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
    this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
    this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
    this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
    this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
    this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
    this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
    this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));

    // Only register config value editors if explicitly requested.
    if (this.configValueEditorsActive) {
        StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
        this.defaultEditors.put(String[].class, sae);
        this.defaultEditors.put(short[].class, sae);
        this.defaultEditors.put(int[].class, sae);
        this.defaultEditors.put(long[].class, sae);
    }
}

BeanWrapper 接口的默认实现类 BeanWrapperImpl 的类结构图如下

BeanWrapperImpl类图

// Spring后来提供的一个进行类型转换的体系,用来取代PropertyEditor
// PropertyEditor有很大的局限性,只能进行String->Object的转换
// 一般使用具体实现类 DefaultConversionService
public interface ConversionService {

	boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);

	boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

	@Nullable
	<T> T convert(@Nullable Object source, Class<T> targetType);

	@Nullable
	Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);

}

具体的调用方法我们就不深究了,但是至少通过这个方法我们已经知道了在Spring中定义了上面一系列常用的属性编辑器使我们可以方便地进行配置。

如果我们定义的bean中的某些属性类型不在上面的常用配置中的话,才需要我们进行个性化的属性编辑器的注册。

3、给予Aware接口实现类相应资源

了解了属性编辑器的使用后接下来我们继续通过AbstractApplicationContext的prepareBeanFactory方法的主线来进行方法跟踪。对于beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));其实主要目的就是注册BeanPostProcessor,而真正的逻辑还是在 ApplicationContextAwareProcessoer 中。

ApplicationContextAwareProcessoer实现BeanPostProcessoer接口,我们回顾下之前讲过的内容,在bean实例化时,也就是Spring激活bean的init-method的前后,会调用BeanPostProcessoer的postProcessBeforeInitialization方法和postProcessAfterInitialization方法。同样,对于ApplicationContextAwareProcessoer我们也关心这两个方法。

对于postProcessAfterInitialization方法,在ApplicationContextAwareProcessor中并没有做过多的逻辑处理

// 来自于ApplicationContextAwareProcessor
public Object postProcessAfterInitialization(Object bean, String beanName) {
	return bean;
}

public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
    AccessControlContext acc = null;

    if (System.getSecurityManager() != null &&
            (bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
                    bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
                    bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)) {
        acc = this.applicationContext.getBeanFactory().getAccessControlContext();
    }

    if (acc != null) {
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                // 重点看下这一行
                invokeAwareInterfaces(bean);
                return null;
            }
        }, acc);
    }
    else {
        invokeAwareInterfaces(bean);
    }

    return bean;
}

private void invokeAwareInterfaces(Object bean) {
    if (bean instanceof Aware) {
        if (bean instanceof EnvironmentAware) {
            ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
        }
        if (bean instanceof EmbeddedValueResolverAware) {
            ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(
                    new EmbeddedValueResolver(this.applicationContext.getBeanFactory()));
        }
        if (bean instanceof ResourceLoaderAware) {
            ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
        }
        if (bean instanceof ApplicationEventPublisherAware) {
            ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
        }
        if (bean instanceof MessageSourceAware) {
            ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
        }
        // 这个比较常见
        if (bean instanceof ApplicationContextAware) {
            ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
        }
    }
}

postProcessBeforeInitialization方法中调用了invokeAwareInterfaces方法。从该方法中,我们或许已经或多或少了解了Spring的用意,实现这些Aware接口的bean在被初始化之后,可以取得一些对应的资源。

4、ignoreDependencyInterface

当Spring将ApplicationContextAwareProcessor注册后,那么在invokeAwareInterfaces方法中间调用Aware类已经不是普通的bean了,Spring会自动注入,此时需要Spring做bean依赖注入的时候忽略他们(例如不需要@Autowired)。而ignoreDependencyInterface的作用正是如此。

beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

5、registerResolvableDependency

Spring中有了忽略依赖的功能,当然也必不可少地会有注册依赖的功能。

beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);

当注册了依赖解析后,例如当注册了对BeanFactory.class的解析依赖后,当bean的属性注入的时候,一旦检测到属性为BeanFactory类型便会将beanFactory的实例注入进去,其他同。

六、postProcessBeanFactory

BeanFactory作为Spring中容器功能的基础,用于存放所有已经加载的bean,为了保证程序的高可扩展性,Spring针对BeanFactory做了大量的扩展。

// 来源于类 AbstractRefreshableWebApplicationContext
// 注意,这一步注册了Scope
// 这里似乎有点奇怪,为什么呢,因为这个逻辑里面我们并没有看见对于BeanFactoryPostProcessor的处理
// 其实这一步做的还是调用前的一些准备工作,真正的调用是在下一步
/**
 * Register request/session scopes, a {@link ServletContextAwareProcessor}, etc.
 */
// 关注微信公众号:好看的HK,第一时间掌握最新动态!
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    // 添加对于servletContext和servletConfig的注入
    beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig));
    // 又添加了两个Aware
    beanFactory.ignoreDependencyInterface(ServletContextAware.class);
    beanFactory.ignoreDependencyInterface(ServletConfigAware.class);

    WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig);
}

// 下面3个来自于 ServletContextAwareProcessor
public ServletContextAwareProcessor(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
    this.servletContext = servletContext;
    this.servletConfig = servletConfig;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    if (getServletContext() != null && bean instanceof ServletContextAware) {
        ((ServletContextAware) bean).setServletContext(getServletContext());
    }
    if (getServletConfig() != null && bean instanceof ServletConfigAware) {
        ((ServletConfigAware) bean).setServletConfig(getServletConfig());
    }
    return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    return bean;
}

/**
 * Register web-specific scopes ("request", "session", "globalSession", "application")
 * with the given BeanFactory, as used by the WebApplicationContext.
 * @param beanFactory the BeanFactory to configure
 * @param sc the ServletContext that we're running within
 */
// 区别于传统的singelton、prototype域,这里添加的web环境专有的域
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
        @Nullable ServletContext sc) {
    beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
    beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
    if (sc != null) {
        ServletContextScope appScope = new ServletContextScope(sc);
        beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
        // Register as ServletContext attribute, for ContextCleanupListener to detect it.
        sc.setAttribute(ServletContextScope.class.getName(), appScope);
    }
    beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
    beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
    beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
    beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
    if (jsfPresent) {
        FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
    }
}

/**
 * Register web-specific environment beans ("contextParameters", "contextAttributes")
 * with the given BeanFactory, as used by the WebApplicationContext.
 * @param bf the BeanFactory to configure
 * @param servletContext the ServletContext that we're running within
 * @param servletConfig the ServletConfig
 */
// 添加web环境专有bean,方便使用者调用
public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf,
        @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
    if (servletContext != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) {
        bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, servletContext);
    }
    if (servletConfig != null && !bf.containsBean(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME)) {
        bf.registerSingleton(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME, servletConfig);
    }
    if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) {
        Map<String, String> parameterMap = new HashMap<>();
        if (servletContext != null) {
            Enumeration<?> paramNameEnum = servletContext.getInitParameterNames();
            while (paramNameEnum.hasMoreElements()) {
                String paramName = (String) paramNameEnum.nextElement();
                parameterMap.put(paramName, servletContext.getInitParameter(paramName));
            }
        }
        if (servletConfig != null) {
            Enumeration<?> paramNameEnum = servletConfig.getInitParameterNames();
            while (paramNameEnum.hasMoreElements()) {
                String paramName = (String) paramNameEnum.nextElement();
                parameterMap.put(paramName, servletConfig.getInitParameter(paramName));
            }
        }
        bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME,
                Collections.unmodifiableMap(parameterMap));
    }

    if (!bf.containsBean(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME)) {
        Map<String, Object> attributeMap = new HashMap<>();
        if (servletContext != null) {
            Enumeration<?> attrNameEnum = servletContext.getAttributeNames();
            while (attrNameEnum.hasMoreElements()) {
                String attrName = (String) attrNameEnum.nextElement();
                attributeMap.put(attrName, servletContext.getAttribute(attrName));
            }
        }
        bf.registerSingleton(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME,
                Collections.unmodifiableMap(attributeMap));
    }
}

篇幅所限,本节最后介绍类 AbstractRefreshableWebApplicationContext 中的 postProcessBeanFactory BeanFactory后处理方法做的一些针对Web环境的前置操作(增加ServletContextAwareProcessor、过滤特定Aware、注册web专有scope、设置全局属性)。

下节将探讨 BeanFactoryPostProcessor 的调用过程。读者这里需要注意区分 postProcessBeanFactory 和 BeanFactoryPostProcessor。一个是BeanFactory初始化后的收尾动作,属于狭义的硬编码动作;一个是专门针对BeanFactory的PostProcessor(后处理器,类比BeanPostProcessor),属于可插拔式扩展组件,用于定制BeanFactory,两者是完全不同的含义。

总访问次数: 7次, 一般般帅 创建于 2020-01-15, 最后更新于 2024-10-14

进大厂! 欢迎关注微信公众号,第一时间掌握最新动态!